﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using HeritageAcademy.Models.Filters;
using HeritageAcademy.Models.Loaders;
using HeritageAcademy.Shared;

namespace HeritageAcademy.Models.ViewModels
{
    public class LibraryViewModel : ViewModelBase
    {
        #region Parameters 
        
        private readonly LibraryFilter _libraryFilter;
        private CourseViewModel _highlightedCourse;
        private CourseViewModel _lastSelectedCourse;
        private ObservableCollection<CourseViewModel> _courses;
        
        #endregion

        #region Ctor 
        
        public LibraryViewModel()
        {
            FilterStack = new ObservableCollection<CourseFilter>();
            Courses = new ObservableCollection<CourseViewModel>();

            _libraryFilter = new LibraryFilter(this) { IsFirstInStack = true };

            PushFilter(_libraryFilter);
            InitCourseList();
        }
        
        #endregion

        #region Properties 
        
        public CourseViewModel HighlightedCourse
        {
            get { return _highlightedCourse; }
            set
            {
                if (value != null) _lastSelectedCourse = value;
                _highlightedCourse = value;
                RaisePropertyChanged("HighlightedCourse");
            }
        }
        
        #endregion 

        #region Public Methods 
        
        public void NotifyUnload()
        {
            // Notify last selected book about unloading
            if (_lastSelectedCourse != null)
                _lastSelectedCourse.NotifyUnload();
        }

        public CourseViewModel GetCourseViewModel(string filename)
        {
            var courseViewModel = (from course in Courses
                                where course.Model.ZipPath == filename
                                select course).SingleOrDefault();

            // handle case where book is not in library
            if (courseViewModel == null)
            {
                var newFileName = CopyCourseToFolder(filename);
                var course = CourseLoader.GetFromZip(newFileName);
                courseViewModel = new CourseViewModel(course);
                Courses.Add(courseViewModel);
                UpdateCourseFilters();
            }

            return courseViewModel;
        }

        #endregion

        #region Private Methods 
        
        private static string CopyCourseToFolder(string filename)
        {
            var newFilename = Path.Combine(IOHelper.DefaultCoursesFolders.FirstOrDefault(), Path.GetFileName(filename));
            if (File.Exists(newFilename)) return newFilename;
            File.Copy(filename, newFilename);
            return newFilename;
        }

        private void UpdateCourseFilters()
        {
            var last = PeekFilter();
            if (last == null) return;
            last.RefreshResults();
            if (last.Results == null) return;
            var filteredCourses = last.Results;
            var hasChanges = Courses.Aggregate(false, (current, course) => 
                                                current || 
                                                ((filteredCourses.Contains(course) && !course.IsFiltered) || 
                                                (!filteredCourses.Contains(course) && course.IsFiltered)));

            if (!hasChanges) return;
            RaiseFilteredCoursesChanging();

            foreach (var course in Courses)
                course.IsFiltered = filteredCourses.Contains(course);

            RaiseFilteredCoursesChanged();
        }
        
        #endregion

        #region Courses
        
        public ObservableCollection<CourseViewModel> Courses
        {
            get { return _courses; }
            private set
            {
                _courses = value;
                // TODO: Rename Books to Courses
                RaisePropertyChanged("Books");
            }
        }

        private void InitCourseList()
        {
            foreach (var dir in IOHelper.DefaultCoursesFolders)
                LoadCoursesFromFolder(dir);
            UpdateCourseFilters();
        }

        private void LoadCoursesFromFolder(string sourceFolder)
        {
            // TODO: Async + move the book loading into the shared
            var courses = CourseLoader.LoadCourses(sourceFolder);
            if (!courses.Any())
            {
                Courses.Clear();
                return;
            }

            foreach (var course in courses.Select(b => new CourseViewModel(b)))
                Courses.Add(course);
        }
        
        #endregion

        #region FilterStack
        
        public ObservableCollection<CourseFilter> FilterStack { get; private set; }

        public void PushFilter(CourseFilter filter)
        {
            var last = PeekFilter();
            if (last != null)
            {
                last.IsLastInStack = false;
            }

            FilterStack.Add(filter);

            filter.IsLastInStack = true;
            RaisePropertyChanged("FilterStack");

            UpdateResults();
            UpdateCourseFilters();
        }

        public CourseFilter PeekFilter()
        {
            return FilterStack.Count > 0 ? FilterStack.Last() : null;
        }

        public CourseFilter PopFilter()
        {
            var filter = FilterStack.Last();

            if (FilterStack.Count > 1)
            {
                FilterStack.RemoveAt(FilterStack.Count - 1);
                RaisePropertyChanged("FilterStack");
                UpdateResults();
                UpdateCourseFilters();
            }

            return filter;
        }

        /// <summary>
        /// Pops filters off the stack until the provided filter is on top of the stack 
        /// or the stack is empty (and just the default filter remains)
        /// </summary>
        public CourseFilter PopToFilter(CourseFilter filter)
        {
            while (FilterStack.Count > 0)
            {
                if (FilterStack.Last() == filter) return filter;
                PopFilter();
            }

            return FilterStack.Count > 0 ? FilterStack.Last() : null;
        }

        /// <summary>
        /// If the provided filter is already in the stack, then we pop to it. Else, if it is a valid
        /// subfilter of the top of the stack then we push it. Otherwise, we do nothing.
        /// </summary>
        public void PushOrPopToFilter(CourseFilter filter)
        {
            // check to see if its already in our history
            if (FilterStack.Contains(filter))
            {
                PopToFilter(filter);
                return;
            }

            var last = PeekFilter() as FilteredCourseFilter;
            if (last != null && last.SubFilters.Contains(filter)) PushFilter(filter);
        }
        
        #endregion

        #region Results
        
        /// <summary>
        /// A collection that we can bind to that represents the results that should
        /// be displayed in the LibraryNavigation area.
        /// </summary>
        public IEnumerable DisplayResults { get; set; }

        private void UpdateResults()
        {
            var topFilter = PeekFilter();
            if (topFilter == null) return;
            if (topFilter is FilteredCourseFilter && topFilter.HasSubFilters)
            {
                // return a collection of subfilters
                (topFilter as FilteredCourseFilter).RefreshSubFilters();
                DisplayResults = (topFilter as FilteredCourseFilter).SubFilters.AsEnumerable();
                RaisePropertyChanged("DisplayResults");
            }
            else
            {
                // return the book results
                topFilter.RefreshResults();
                DisplayResults = topFilter.Results;
                RaisePropertyChanged("DisplayResults");
            }
        }
        
        #endregion

        #region Event Handlers
        
        /// <summary>
        /// Raised before a change occurs to the set of courses returned by the filters.
        /// </summary>
        public event EventHandler FilteredCoursesChanging;
        private void RaiseFilteredCoursesChanging()
        {
            if (FilteredCoursesChanging != null)
                FilteredCoursesChanging(this, new EventArgs());
        }

        /// <summary>
        /// Raised after a change occurs to the set of courses returned by the filters.
        /// </summary>
        public event EventHandler FilteredCoursesChanged;
        private void RaiseFilteredCoursesChanged()
        {
            if (FilteredCoursesChanged != null)
                FilteredCoursesChanged(this, new EventArgs());
        }
        
        #endregion
    }
}